或許大家應該都贊同;一個精美流暢的使用者介面可以協助提升使用者體驗,增加黏著度。
Flutter 支援很多方式可以加入樣式。首先我們會聚焦在靜態圖片,組件在閒置時、互動,啟用等情況下的外觀。然後進一步在後續學習使用動畫以及圖片。這大概是相對有趣的部分,因為可以讓組件產生明顯的變化。
在之前的範例我們使用了 Text
,Image
和 Container
等等組件來組織佈局,呈現內容。我們已經知道一些基本改變呈現的方式,接著我們將進一步深入:
任何時候當我們希望變更組件的顏色的時候,必須了解如何使用顏色 Color
類別。其中最簡單的方式就是使用包含了許多內建的顏色的 Colors
。在之前的範例我們已經使用過了例如:
Container(
color: Colors.grey, // <- 這裡
)
如我們所預期的,Colors.grey
會回傳對應的顏色。除了預設顏色之外,還可以進一步指定顏色深淺。若你使用過 Material Design 設計規範或相關框架應該理解調色盤色票。Material Design 規範的色彩系統,也就是包含了一些預先定義的色票。每一個主色還定義了 50, 100 - 900 的漸層色號,數字越小越淡。
下面表示相同顏色,你也可以調整 50, 100 - 900 的深淺顏色。
Colors.grey
Colors.grey[400]
Colors.grey.shade400
若你不想使用預先建立的色彩系統,你可以使用 Hex 或 RGB
var color = Color(0xff32a852);
上面程式碼會使用 Hex 構建一個 Color
的物件,0x
表示是一個 16 進制值。
常見的 Hex 顏色參數使用 6 位來表達例如在 CSS 中的 #0000ff
。但在 Flutter 中,Color
預設建構子不支持 6 位的 Hex 顏色值。Color
類別是一個不可變的 32 位 ARGB 顏色值。因此須使用 8 位來表示,前兩位表示透明度。
當你看到 Color(0xFF00FF00)
,這個顏色值是按照 ARGB 的方式來解釋的:
FF
是 Alpha (透明度) 值,表示完全不透明。00
是 Red (紅色) 值。FF
是 Green (綠色) 值。00
是 Blue (藍色) 值。透明度的部分和一般我們使用的 0 ~ 100
或 0.x ~ 1
設值方式不太一樣,也不太直覺。它的計算方式是 255 * [希望的%] ~= 設定值
。例如 50% 255 * 0.5 ≈ 128,在 16 進制中為 80
其他範例:
Color c1 = const Color(0xFF42A5F5);
Color c2 = const Color.fromARGB(0xFF, 0x42, 0xA5, 0xF5);
Color c3 = const Color.fromARGB(255, 66, 165, 245);
Color c4 = const Color.fromRGBO(66, 165, 245, 1.0);
除了單一色,Flutter 也支持漸層色,可以通過 LinearGradient
或 RadialGradient
實現:
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Colors.blue, Colors.green],
)
),
)
文字算是所有組件中最基本與常用的元素之一。前面我們已經知道可以使用 Text
來顯示內容
Text("我們的內容")
Text
很常作為其他組件的子元素用於顯示文字內容,如果沒有特別指定樣式,一般會套用定義在應用程式的 Theme
的預設字體樣式。如果要自訂樣式,可以使用 style
,這個參數便是為了這個需求而設計的。
該參數需要搭配TextStyle
物件作為參數,下面就是 TextStyle
一些主要參數:
backgroundColor
用於設置文字背景色。同樣接受一個 Color
物件作為值。color
此參數可以設定字體顏色,一樣使用 Color
為其值。decoration
用於為文字裝飾,如下劃線、上劃線或刪除線等。可以使用 TextDecoration
類進行設置,如 TextDecoration.underline
表示下劃線。fontSize
用於設置字體的大小。在 Flutter 中,字體大小使用邏輯像素為單位 px
,這是一種與設備無關的單位,會根據設備的像素密度自動縮放,以確保在不同解析度的螢幕上字體大小保持一致。一般來說,小字的大小約為 6-8 px,一般字為 12-16 px ,較大的字體大約落在 30-60 px。fontWeight
用於設置字體的粗細。它使用 FontWeight
類進行設置,其值範圍從 FontWeight.w100
(最細)到 FontWeight.w900
(最粗)。fontStyle
設置字體的樣式,如斜體。可以使用 FontStyle
類進行設置,如 FontStyle.italic
表示斜體。wordSpacing
和 letterSpacing
這兩個屬性分別控制單詞之間和字母之間的間距。它們接受一個 double
值,表示間距的大小。overflow
當文字內容超出其容器的範圍,且沒有使用捲軸時,可以使用 overflow
屬性控制文字的顯示方式。它接受 TextOverflow
enum 值,如 TextOverflow.clip
(裁切多餘的文字)、TextOverflow.ellipsis
(在末尾顯示省略號 ... )或 TextOverflow.fade
(多餘的文字淡出)。下面為使用範例:
Text(
"你的文字",
style: TextStyle(
backgroundColor: Color.fromRGBO(10, 150, 80, 1),
color: Colors.red,
fontSize: 16,
fontWeight: FontWeight.w300,
fontStyle: FontStyle.italic,
letterSpacing: 2,
wordSpacing: 3,
overflow: TextOverflow.fade,
decoration: TextDecoration.underline
),
)
如果你有 CSS 的開發經驗,可能會發現 Flutter 中的一些值需要使用特定的物件或 enum 進行設定,而不是直接指定具體的值。這是因為 Flutter 使用了強型別的設計,提供了更好的型別安全性和可讀性。但確實新手需要花點時間習慣。
另外,3.x 之後還支援了 RichText
和 TextSpan
協助我們處理複雜的文字組合呈現:
RichText(
text: TextSpan(
text: '哈嘍 ',
style: DefaultTextStyle.of(context).style,
children: const <TextSpan>[
TextSpan(text: '粗體字', style: TextStyle(fontWeight: FontWeight.bold)),
TextSpan(text: ' 世界!'),
],
),
)
Container
是一個排版蠻常使用的組件,一樣也可以定義樣式。主要使用 decoration
參數搭配 Decoration
類別或者更準確的說,是 Decoration
的子類別,因為 Decoration
的用途類似於抽象類別。
雖然這裡我們是用 Container
作為例子,但這些知識也適用於其他有 decoration
的組件。
對照上面的
TextDecoration
你可以更清楚所謂的子類別。
下面是一般 Container
的範例
Container(
child: Text(widget.name)
)
在容器內部我們傳入了 Text
,接著讓我們為 Container
加入一些樣式例如簡單的下方邊線,這個效果會很接近文字的底線,但這次我們不是使用 TextStyle,而是使用 BoxDecoration
如下
Container(
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(
color: Colors.blue,
width: 3,
),
)),
child: Text(widget.name)
)
上面範例我們使用了 BoxDecoration
然後使用 border
參數和其對應設值的物件。我們可以使用 Border
個別指定每一邊的樣式或者使用 Border.all
、Border.symmetric
工廠建構子來設定。上面例子我們指定了 bottom
的樣式。
除此之外,為了讓你更快的熟悉實戰,我們也加入了 const
,在 Flutter 專案中為了改善效能,如果不會變動的組件建議使用 const
一般來說你的開發編輯器應該也會提示你。
總之,上面範例我們在文字下方建立了一個很像底線的效果。容器有很多樣式可以使用,後續你應該需要花點時間學習這些效果如何使用和搭配,這裡我們再提供另一個範例:
Container(
decoration: ShapeDecoration(
shape: StadiumBorder(
side: BorderSide(
color: Colors.green,
width: 6,
),
),
),
child: Text(widget.name)
)
Container(
width: 300,
height: 200,
decoration: BoxDecoration(
image: DecorationImage(
image: NetworkImage('https://example.com/image.jpg'),
fit: BoxFit.cover,
),
borderRadius: BorderRadius.circular(20),
),
)
在之前的範例我們已經使用過一些按鈕組件,Flutter 提供了多種按鈕組件,如 ElevatedButton
,TextButton
,和 OutlinedButton
。
這些按鈕可以使用 ButtonStyle
類別作為 style
參數的值。我們可以選擇需要的樣式屬性,只要在 ButtonStyle
覆寫屬性即可,未定義的屬性將保持預設值。
例如要修改 ElevatedButton
的對齊方式如下:
ElevatedButton(
onPressed: () {},
child: Text("按鈕"),
style: ButtonStyle(
alignment: Alignment.centerLeft,
),
)
常用的 ButtonStyle
如下:
backgroundColor
: 設置按鈕的背景色。foregroundColor
: 設置按鈕的前景色,即文字和圖標的顏色。overlayColor
: 設置按鈕在按下時的覆蓋色。shadowColor
: 設置按鈕的陰影顏色。elevation
: 設置按鈕的陰影高度。padding
: 設置按鈕的內邊距。shape
: 設置按鈕的形狀,例如矩形、圓角矩形、圓形等。side
: 設置按鈕的邊框。textStyle
: 設置按鈕文字的樣式。然而一個按鈕包含了多個不同的狀態,例如活動狀態 Active,停用 Disable,被點擊等,樣式需要配合這些狀態。一個方法是使用 MaterialStateProperty
或其子類別如 MaterialStateColor
支援讀取按鈕狀態,你就可以決定要設定什麼樣式。
例如:我們希望按鈕背景樣式是綠色,但被按下的時候變藍色
ElevatedButton(
onPressed: () {},
child: Text("按鈕"),
style: ButtonStyle(
backgroundColor: MaterialStateProperty.resolveWith<Color>(
(Set<MaterialState> states) {
if (states.contains(MaterialState.pressed)) {
return Colors.blue;
}
return Colors.green;
}
)
)
)
這樣的使用方式,第一次看到的時候也許會感到很困惑,尤其是一些已經很習慣網頁應用程式的開發者。不過上面我們讓背景色根據狀態產生變化。通過 resolveWith
靜態方法和其提供的 MaterialState
然後依據狀態回傳顏色。
以下是一些常見的 MaterialState
值:
MaterialState.pressed
: 按鈕處於被按下的狀態。MaterialState.hovered
: 按鈕處於被懸停的狀態。MaterialState.focused
: 按鈕處於獲得焦點的狀態。MaterialState.disabled
: 按鈕處於禁用的狀態。MaterialState.error
: 按鈕處於錯誤的狀態。如同一開始提到的 MaterialStateColor
是 MaterialStateProperty
的子類別,因此下面的寫法也是等價的:
backgroundColor: MaterialStateColor.resolveWith((states) {
if (states.contains(MaterialState.pressed)) {
return Colors.blue;
}
return Colors.green;
})
ButtonStyle
的其他屬性也可以使用類似 MaterialStateProperty
的用法,讓我們能夠在按鈕不同狀態下靈活控制樣式。
到此希望讓你對於 Flutter 中按鈕組件動態樣式的處理有了基本的概念。這種方法雖然初看起來可能有些複雜,但它提供了非常靈活的方式來處理不同狀態下的樣式變化。
Flutter 的佈局系統支援不同螢幕尺寸的對應不同的設計,即網頁常見的響應式設計。LayoutBuilder
是實現這一目標的關鍵組件
LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
if (constraints.maxWidth > 600) {
return WideLayout();
} else {
return NarrowLayout();
}
},
)
通過熟悉這些組件的使用,我們將越來越具備開發自己心目中或者需求應用程式的能力。